/** 
 * @file Events.h 
 * 
 * @brief  Declares Event, EventArgs and related classes and functions
 * @author Bas du Pre
 * @date   19-05-2011
 * 
 **/ 


#pragma once

#include "../boost/function.hpp"
#include "../boost/mem_fn.hpp"
#include "../boost/bind.hpp"

#include "PObject.h"
#include "LinkedList.h"
#include "BaseEvent.h"
#include "functions.h"


namespace Prism {

    /**
     * \brief Base class to represent event arguments.
     *
     * To pass arguments to event handlers it is encouraged, though not obliged, 
     * to use a subclass of EventArgs.
     * This EventArgs baseclass can be used if a event does not have arguments.
     * \note This class does not have any fields that can be used to store data.
     *       You should use or create a subclass of EventArgs for that.
     */
    class EventArgs {
        
    };
    
    /** \cond INTERNAL */
    template <class TEventArgs, class TClass>
    class SubscribeMethodDescription {
    public:
        TClass* object;
        void (TClass::*methodAddress)(void* sender, TEventArgs e);
    };
    
    template <class TEventArgs>
    class EventHandlerWrapper {
    public:
        boost::function<void (void*, TEventArgs)> *eventHandler;
        PObject *object;
        
        EventHandlerWrapper(boost::function<void (void*, TEventArgs)> *eventHandler,
                            PObject *object) : eventHandler(eventHandler),
                            object(object) {}
    };
    /** \endcond */
    
    /**
     * \brief Class that represents an event.
     * \tparam TEventArgs the type to be used to pass event arguments to event handlers.
     *
     * Events can be used to trigger actions by using the () operator. An event
     * will then call all event handlers that have been linked to this event.
     * This can be very useful in environments with much user or external input. It is much
     * easier and quicker to link some behaviour to an event than to call a list
     * of functions that perform that behaviour yourself every time something has occurred.
     *
     * An event handler can be linked to an Event by using the addHandler method
     * or the convenient += operator.
     *
     * \note Although Event objects can be defined and retained anywhere,
     *       <i>event handlers</i> can only be methods of a subclass of PObject.
     *
     * To simplify creating a reference to a method for use as an event handler
     * it is encouraged to use the delegate() function.
     * \section Example
\code Event<EventArgs *> onTick;
     
class MyClass : public PObject {
    public:
        void tickHandler(void* sender, EventArgs* e) {
            std::cout << "tickHandler(): I have been called!" << std::endl;
        }
};

void main() {
    MyClass *m = new MyClass; 
    onTick += delegate(m, &MyClass::tickHandler); // link the event handler to onTick
    EventArgs e;
    onTick(m, &e); // fire the event. (m->tickHandler will be called with these arguments)
}
\endcode
     */
    template <class TEventArgs>
    class Event /** \cond INTERNAL */ : private BaseEvent /** \endcond */
    {
    public:
        ~Event()
        {
            removeAllHandlers();
        }
         
        /** \cond INTERNAL */
        typedef boost::function<void (void*, TEventArgs)> EventHandler;
        /** \endcond */
        
        /**
         * \brief Fire this event.
         * \param sender A reference to the object that caused this event to be fired.
         * \param args An object that possibly contains information about why the event was fired.
         *
         * This will result in all event handlers to be called with the same parameters that has
         * been passed to this call.
         *
         * If no event handler has been linked firing an event will do nothing.
         *
         */
        inline void operator() (void* sender, TEventArgs args) 
        {            
            for (unsigned int i = 0; i < handlers.length(); i++)
                (*(handlers[i]->eventHandler))(sender, args);
        }
        
        /** \cond INTERNAL */
        template <class TClass>
        void addHandler(TClass* object, void (TClass::*methodAddress)(void* sender, TEventArgs e))
        {
            if (!isType<PObject>(object))
            {
                PRISM_ERROR("Event System : Class must be a subclass of PObject to attach eventhandlers.");
                return;
            }
            
            PObject *pobject = (PObject*)object;
            this->linkEvent(pobject);
            EventHandler *h = new EventHandler(boost::bind(boost::mem_fn(methodAddress), object, _1, _2));
            handlers.pushBack(new EventHandlerWrapper<TEventArgs>(h, pobject));
            
            return;
        }
        
        template <class TClass>
        Event<TEventArgs>& operator+=(SubscribeMethodDescription<TEventArgs, TClass> s)
        {
            addHandler(s);
            return *this;
        }
        
        virtual void removeHandler(PObject *object)
        {
            for (unsigned int i = 0; i < handlers.length(); i++)
            {
                if (handlers[i]->object == object) 
                {
                    delete handlers[i]->eventHandler;
                    delete handlers[i];
                    handlers.removeAt(i);
                    this->unlinkEvent((PObject*)object);
                }
            }
        }
    
        template <class TClass>
        void removeHandler(TClass *object, void (TClass::*methodAddress)(void* sender, TEventArgs e))
        {
            for (int i = 0; i < handlers.length(); i++)
            {
                // TODO kijken of deze comparison werkt
                EventHandler *h2 = new EventHandler(boost::bind(boost::mem_fn(methodAddress), object, _1, _2));
                if (handlers[i].object == object && *handlers[i].eventHandler == *h2) 
                {
                    delete handlers[i].eventHandler;
                    delete handlers[i];
                    handlers.removeAt(i);
                    ((PObject*)object)->unlinkEvent(this);
                }
            }
        }
        
        template <class TClass>
        Event<TEventArgs>& operator-=(SubscribeMethodDescription<TEventArgs, TClass> s)
        {
            removeHandler(s.object, s.methodAddress);
            return *this;
        }
        /** \endcond */
        
#ifdef PRISM_DOXYGEN
        /**
         * \brief Attach an event handler to this Event.
         * \param object A pointer to an instance of a PObject subclass.
         * \param methodAddress A pointer to a method definition in the class of the object.
         * 
         * The method pointing to by \c methodAddress will be called on object \c object
         * when this Event is fired.
         *
         * \note methodAddress is a pointer to the <i>definition</i> in the class of the object. This means
         *       you should pass a reference like in this example:
         *       \code event.addHandler(myNode, &MyNode::theEventHandler); // myNode is a pointer to an instance of MyNode. \endcode
         */
        void addHandler(PObject* object, (Method) *methodAddress);
        
        /**
         * \brief Attach an event handler to this Event by passing a delegate.
         * \param delegate A delegate to an event handler
         * 
         * \note You can construct a delegate using the delegate() function.
         */
        Event<TEventArgs>& operator+=(delegate);
        
        /**
         * \brief Remove all event handlers of instance <i>object</i> linked to this event.
         * \param object A pointer to an instance of a subclass of PObject.
         * 
         */
        virtual void removeHandler(PObject *object);
        
        /**
         * \brief Unlink an event handlers from this event.
         * \param object A pointer to an instance of a PObject subclass.
         * \param methodAddress A pointer to a method definition in the class of the object.
         * 
         * \note methodAddress is a pointer to the <i>definition</i> in the class of the object. This means
         *       you should pass a reference.
         */
        /// \todo This method contains an comparison that possibly does not work as intended. Someone should
        ///       take a look at it. <i>It is therefor possible this method does not work as expected!</i> 
        ///       Please report any observations.
        void removeHandler(PObject *object, (Method) *methodAddress);
        
        /**
         * \brief Unlink an event handlers from this event by passing a delegate.
         * \param delegate A delegate to an event handler
         * 
         * \note You can construct a delegate using the delegate() function.
         */
        Event<TEventArgs>& operator-=(delegate);
#endif
        
        /// Remove all event handlers linked to this event.
        void removeAllHandlers()
        {
            for (unsigned int i = 0; i < handlers.length(); i++)
            {
                delete handlers[i]->eventHandler;
                delete handlers[i];
                this->unlinkEvent(handlers[i]->object);
                handlers.removeAt(i);
            }
        }
        
        /// Checks if this event has any event handler linked.
        bool hasHandler() {
            return !handlers.empty();
        }
                
    private:
        template <class TClass>
        void addHandler(SubscribeMethodDescription<TEventArgs, TClass> &s)
        {
            addHandler(s.object, s.methodAddress);
        }
        
        NoPObjectList<EventHandlerWrapper<TEventArgs> *> handlers;
    };
        
    /** \cond INTERNAL */
    template <class TEventArgs, class TClass>
    SubscribeMethodDescription<TEventArgs, TClass> delegate(TClass* object, void (TClass::*methodAddress)(void* sender, TEventArgs e))
    {
        SubscribeMethodDescription<TEventArgs, TClass> s;
        s.object = object;
        s.methodAddress = methodAddress;
        return s;
    }
    /** \endcond */
    
    #ifdef PRISM_DOXYGEN
    /**
     * \relates Event
     * \brief Create a delegate to a method on an instance of a subclass of PObject.
     * \param object A pointer to an instance of a PObject subclass.
     * \param methodAddress A pointer to a method definition in the class of the object.
     * \return A delegate describing the method call.
     *
     * \note methodAddress is a pointer to the <i>definition</i> in the class of the object. This means
     *       you should pass a reference like in this example:
     *       \code delegate(myNode, &MyNode::theEventHandler); // myNode is a pointer to an instance of MyNode. \endcode
     */
    delegate(PObject* object, (Method) *methodAddress);
    #endif
    
}
